/*****************************************************************************
 * Copyright (C) 2008 EnterpriseDB Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, see http://www.gnu.org/licenses or write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 *
 * You can contact EnterpriseDB, Inc. via its website at
 * http://www.enterprisedb.com
 *
 ****************************************************************************/
/*
 *
 *
 */

package com.edb.gridsql.parser;

import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Vector;

import com.edb.gridsql.engine.Engine;
import com.edb.gridsql.engine.ExecutionResult;
import com.edb.gridsql.engine.IPreparable;
import com.edb.gridsql.engine.XDBSessionContext;
import com.edb.gridsql.exception.XDBSecurityException;
import com.edb.gridsql.exception.XDBServerException;
import com.edb.gridsql.metadata.DBNode;
import com.edb.gridsql.metadata.SyncDropTable;
import com.edb.gridsql.metadata.SysDatabase;
import com.edb.gridsql.metadata.SysForeignKey;
import com.edb.gridsql.metadata.SysLogin;
import com.edb.gridsql.metadata.SysReference;
import com.edb.gridsql.metadata.SysTable;
import com.edb.gridsql.metadata.SysView;
import com.edb.gridsql.metadata.scheduler.LockSpecification;
import com.edb.gridsql.parser.core.syntaxtree.dropTable;
import com.edb.gridsql.parser.core.visitor.ObjectDepthFirst;
import com.edb.gridsql.parser.handler.IdentifierHandler;
import com.edb.gridsql.parser.handler.TableNameHandler;

public class SqlDropTable extends ObjectDepthFirst implements IXDBSql,
        IPreparable {
    private XDBSessionContext client;

    private SysDatabase database;

    private SysTable table;

    private String dropTableSQL;

    private LinkedList<SysTable> refs = null;

    private boolean temporary;

    private String tablename;

    private String referenceName;

    public SqlDropTable(XDBSessionContext client) {
        this.client = client;
        database = client.getSysDatabase();
    }

    /**
     * f0 -> <DROP_> f1 -> <TABLE_> f2 -> TableName(prn)
     */
    @Override
    public Object visit(dropTable n, Object argu) {
        Object _ret = null;
        n.f0.accept(this, argu);
        n.f1.accept(this, argu);
        TableNameHandler aTableNameHandler = new TableNameHandler(client);
        n.f2.accept(aTableNameHandler, argu);
        tablename = aTableNameHandler.getTableName();
        referenceName = aTableNameHandler.getReferenceName();
        temporary = aTableNameHandler.isTemporary();
        return _ret;
    }

    public long getCost() {
        return LOW_COST;
    }

    public Collection<SysTable> getRelatedTables() {
        if (refs == null) {
            refs = new LinkedList<SysTable>();
            if (!isPrepared()) {
                try {
                    prepare();
                } catch (Exception ex) {
                    // Ignore
                }
            }
            Vector<SysReference> vRefFkList = table.getSysFkReferenceList();
            Vector<SysReference> vRefRefList = table.getSysReferences();

            for (SysReference aReferringTab : vRefFkList) {
                refs.add(database.getSysTable(aReferringTab.getRefTableID()));
            }

            for (SysReference aReferringTab : vRefRefList) {
                SysForeignKey aFkey = (SysForeignKey) aReferringTab.getForeignKeys().elementAt(0);
                SysTable refTable = aFkey.getReferringSysColumn(database).getSysTable();
                refs.add(refTable);
            }
        }
        return refs;
    }

    /**
     * Send back the lock specification that we should send back
     *
     * @param aMetadata
     * @return
     */
    public LockSpecification<SysTable> getLockSpecs() {
        return new LockSpecification<SysTable>(
                getRelatedTables(), Collections.singletonList(table));
    }

    /*
     * (non-Javadoc)
     *
     * @see com.edb.gridsql.Parser.IXDBSql#getNodeList()
     */
    public Collection<DBNode> getNodeList() {
        HashSet<DBNode> nodes = new HashSet<DBNode>();
        if (table != null) {
            nodes.addAll(table.getNodeList());
        }
        for (Object element : getRelatedTables()) {
            nodes.addAll(((SysTable) element).getNodeList());
        }
        return nodes;
    }

    public String getTablename() {
        return tablename;
    }

    /**
     * @return
     */
    public boolean isTempTable() {
        return temporary;
    }

    /**
     * @return Returns the referenceName.
     */
    public String getReferenceName() {
        return referenceName;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.edb.gridsql.Engine.IPreparable#isPrepared()
     */
    public boolean isPrepared() {
        return table != null;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.edb.gridsql.Engine.IPreparable#prepare()
     */
    public void prepare() throws Exception {
        try {
            table = database.getSysTable(tablename);
            Enumeration eViews = database.getAllViews();
            while (eViews.hasMoreElements()) {
                SysView view = (SysView) eViews.nextElement();
                if (view.hasDependedTable(table.getTableId())) {
                    XDBSecurityException ex = new XDBSecurityException(
                            "cannot drop table " + tablename
                                    + " because other objects depend on it");
                    throw ex;
                }
            }
            if (client.getCurrentUser().getUserClass() != SysLogin.USER_CLASS_DBA
                    && table.getOwner() != client.getCurrentUser()) {
                XDBSecurityException ex = new XDBSecurityException(
                        "You are not allowed to drop table " + tablename);
                throw ex;
            }
            if (table.getChildrenTables().size() > 0) {
                throw new XDBServerException("Table " + getReferenceName()
                        + " has children tables. Please, remove them first");
            }
            Collection<SysReference> references = table.getSysReferences();
            if (references != null && !references.isEmpty()) {
                throw new XDBServerException("Table " + getReferenceName()
                        + " has foreign references. Please, remove them first");
            }

            dropTableSQL = "DROP TABLE " + IdentifierHandler.quote(tablename);

        } finally {
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.edb.gridsql.Engine.IExecutable#execute(com.edb.gridsql.Engine.Engine)
     */
    public ExecutionResult execute(Engine engine) throws Exception {
        try {

            engine.executeDDLOnMultipleNodes(dropTableSQL, table.getNodeList(),
                    new SyncDropTable(this), client);
            if (temporary) {
                client.deregisterTempTableWithSession(referenceName);
            }
            // Table dropped successfully, so do not try and release table lock
            table = null;
            return ExecutionResult
                    .createSuccessResult(ExecutionResult.COMMAND_DROP_TABLE);

        } finally {
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.edb.gridsql.MetaData.Scheduler.ILockCost#needCoordinatorConnection()
     */
    public boolean needCoordinatorConnection() {
        return false;
    }
}
